home *** CD-ROM | disk | FTP | other *** search
/ GameStar 2004 April / Gamestar_61_2004-04_dvdb.iso / DVDStar / Editace / hltp.exe / {app} / Applications / QuArK / plugins / mapsnapobject.py < prev    next >
Text File  |  2004-01-05  |  11KB  |  328 lines

  1. ########################################################
  2. #
  3. #                         Snap Object Plugin
  4. #                          v1.0, Aug 2001
  5. #                      works with Quark 6.3        
  6. #
  7. #
  8. #                    by tiglari@hexenworld.com     
  9. #
  10. #   You may freely distribute modified & extended versions of
  11. #   this plugin as long as you give due credit to tiglari &
  12. #   Armin Rigo. (It's free software, just like Quark itself.)
  13. #
  14. #   Please notify bugs & improvements to tiglari@planetquake.com
  15. #
  16. ###
  17. ##########################################################
  18.  
  19. #$Header: /cvsroot/quark/runtime/plugins/mapsnapobject.py,v 1.3 2001/08/12 00:24:19 tiglari Exp $
  20.  
  21.  
  22.  
  23. Info = {
  24.    "plug-in":       "Snap objects",
  25.    "desc":          "Snapping objects to faces",
  26.    "date":          "6 Aug 2001",
  27.    "author":        "tiglari",
  28.    "author e-mail": "tiglari@planetquake.com",
  29.    "quark":         "Version 6.3" }
  30.  
  31.  
  32. import quarkx
  33.  
  34. import mapmadsel
  35. import quarkpy.mapentities
  36. import quarkpy.dlgclasses
  37. from tagging import *
  38. from quarkpy.maputils import *
  39.  
  40.  
  41.  
  42. #
  43. # WTF ---
  44. #
  45. # First the function that does the real work is defined, then the interface
  46. # (a popup menu of parents with a Live Button Dialog) is set up.
  47. #
  48.  
  49. #
  50. # --  The Function that Does the Real Work
  51. #
  52. # Returns newface, newobj, rotated and shifted in accordance with the following
  53. #  rather complex scheme.  The idea behind all of this is so that if an object has
  54. #  to be rotated to make the selected plane parallel to the tagged one, this
  55. #  rotation will preserve vertical orientation to the greatest extent possible,
  56. #  that is the object won't tilt unnecessarily in intuitively annoying ways.
  57. #
  58. # Or so I hope.  This is pretty complicated :)
  59. #
  60. # The idea is that the object is swivelled around the Z axis and the tilted around
  61. #  a horizontal axis (both thru its center) so that the selected face is paralell
  62. #  to the tagged one, then the object is moved so that the selected face is the
  63. #  specified distance from the tagged one (along the latter's normal).
  64. #
  65. # The rotations are determined as follows:
  66. #
  67. #   if both planes are horizontal (parallel to the xy plane), no rotations are performed
  68. #
  69. #   if one of the planes is horizontal, no swivelling is performed, but the
  70. #     object is tilted around an axis paralell to a horizontal axis lying in
  71. #     the non-horizontal plane, so that the two planes become parallel.
  72. #
  73. #   otherwise, horizontal axes are found in both planes (the bestaxes function).
  74. #     then the object is swivelled so that these axes are parallel, and then the
  75. #     object is tilted, around the now-swivelled horizontal axis in the selected
  76. #     face, so that the two planes are parallel.
  77. #
  78. # Well actually none of these rotations are actually performed yet, rather a rotation
  79. #     matrix has been assembled and is finally applied to the object.
  80. #
  81. # OK, now, finally, the object is translated along the now identically opposite noramls
  82. #     of the two planes to be the specified distance/
  83.  
  84. def snapObjectToPlane(face, object, tagged, separation, doswivel=1, dotilt=1, doshift=1):
  85.  
  86.     #
  87.     # FIXME: things sometimes get spun around 180 from what they
  88.     #  ought to be, if tagged face is horizontal
  89.     #
  90.     facehoriz=face.axisbase()[0]
  91.     taggedhoriz=tagged.axisbase()[0]
  92.     
  93.     if math.fabs(face.normal*Z_axis)==1 :
  94.         doswivel=0
  95.     
  96.     face2, object2 = face.copy(), object.copy()
  97.     idmat = quarkx.matrix(X_axis, Y_axis, Z_axis)
  98.     if doswivel:
  99.         #
  100.         # think of the taggedhoriz as the x axis, facehoriz
  101.         #  is then some vector making an angle with it, use
  102.         #  atan2 to recover the actual angle
  103.         #
  104.         yax = (Z_axis^taggedhoriz).normalized
  105.         swivelangle=-math.atan2(facehoriz*yax,facehoriz*taggedhoriz)+math.pi
  106.         swivelmat=matrix_rot_z(swivelangle)
  107.     else:
  108.         swivelmat = idmat
  109.     if dotilt:
  110.         facenormal=-swivelmat*face.normal
  111.         #
  112.         # Same idea, but this time we're interested
  113.         #  in the angle that the negative normal of the
  114.         #  tilted makes with the normal of the tagged one
  115.         #
  116.         yax=tagged.normal^taggedhoriz
  117. #        debug('yaxx')
  118.         tiltangle=math.atan2(yax*facenormal,tagged.normal*facenormal)
  119. #        debug('horiz '+`facehoriz`)
  120.         tiltmat=ArbRotationMatrix(taggedhoriz, tiltangle)
  121.     else:
  122.         tiltmat=idmat
  123.     mat = tiltmat*swivelmat
  124.     object2.linear(object.origin,mat)
  125.     face2.linear(object.origin,mat)
  126. #    object2.linear(object.origin,tiltmat)
  127. #    face2.linear(object.origin,tiltmat)
  128. #    debug('linear')
  129.     if doshift:
  130.         pt1 = projectpointtoplane(object2.origin,tagged.normal,face2.dist*face2.normal,tagged.normal)
  131.         pt2 = projectpointtoplane(object2.origin,tagged.normal,tagged.dist*tagged.normal,tagged.normal)
  132. #        debug('separation '+`separation`)
  133.         diff = pt2+separation*tagged.normal-pt1
  134.         object2.translate(diff)
  135.         face2.translate(diff)
  136.     return face2, object2
  137.  
  138.  
  139. # --- The Interface
  140. #
  141. class SnapObjectDlg (quarkpy.dlgclasses.LiveButtonDlg):
  142.     #
  143.     # dialog layout
  144.     #
  145.  
  146.     endcolor = AQUA
  147.     size = (220,170)
  148.     dfsep = 0.35
  149.  
  150.     dlgdef = """
  151.         {
  152.         Style = "9"
  153.         Caption = "Snap Object to Plane"
  154.  
  155.         swivel: = {
  156.           Typ = "X"
  157.           Hint = "If this is checked, the object is swivelled around the Z-axis so that horizontal lines in the tagged and selected faces are parallel"
  158.         }
  159.  
  160.         orient: = {
  161.           Typ = "X"
  162.           Hint = "if this is checked, the object is swivelled and then tilted so the tagged and selected faces are parallel." 
  163.         }
  164.  
  165.         shift: = {
  166.           Typ = "X"
  167.           Hint = "if this is checked, the object is shifted so that the selected plane is the distance specified in 'separate' from the tagged plane."
  168.         }
  169.  
  170.         separation: = {
  171.           Typ = "EF00001"
  172.           Hint = "The separation from the tagged face that the object is moved to, if shift is checked"
  173.         }
  174.         
  175.         sep: = { Typ="S" Txt=""}
  176.  
  177.         buttons: = {
  178.           Typ = "PM"
  179.           Num = "1"
  180.           Macro = "snapobject"
  181.           Caps = "S"
  182.           Txt = "Actions:"
  183.           Hint1 = "Perform the specified movement"
  184.         }
  185.  
  186.         sep: = { Typ="S" Txt=""}
  187.  
  188.         exit:py = {Txt="" }
  189.     }
  190.     """
  191.  
  192.     def snap(self):
  193.         pack=self.pack
  194.         #
  195.         # We need to get a snapped copy of the face so that if we want to adjust
  196.         #   the parameters we can.
  197.         #
  198.         newface, newobj = snapObjectToPlane(pack.face, pack.current, pack.tagged, pack.separation, pack.doswivel, pack.dotilt, pack.doshift)
  199.         undo=quarkx.action()
  200.         undo.exchange(pack.current, newobj)
  201.         pack.editor.ok(undo,'snap object to plane')
  202.         pack.face, pack.current = newface, newobj
  203.         
  204.          
  205.  
  206.  
  207. def macro_snapobject(self, index=0):
  208.     editor = mapeditor()
  209.     if editor is None: return
  210.     if index==1:
  211.         editor.dlg_snapobject.snap()
  212.  
  213. quarkpy.qmacro.MACRO_snapobject = macro_snapobject
  214.  
  215.  
  216. #
  217. # These are for when you want the usual value of a checkbox option to
  218. #   checked (so that it will come up that way first time no fuss
  219. #   in defaults.qrk)
  220. #
  221. def readFlippedSetupCheckVal(attr):
  222.     if quarkx.setupsubset(SS_MAP, "Options")[attr]=="1":
  223.         return ""
  224.     else:
  225.         return "1"
  226.         
  227. def writeFlippedSetupCheckVal(attr, val):
  228.     if val=="1":
  229.         quarkx.setupsubset(SS_MAP, "Options")[attr]=""
  230.     else:
  231.         quarkx.setupsubset(SS_MAP, "Options")[attr]="1"
  232.  
  233.  
  234. def readWithDefault(attr,default):
  235.     val = quarkx.setupsubset(SS_MAP, "Options")[attr]
  236.     if val is None:
  237.         return default
  238.     return val
  239.      
  240. def writeWithDefault(attr, val, default):
  241.     if val==default:
  242.         quarkx.setupsubset(SS_MAP, "Options")[attr]=""
  243.     else:
  244.         quarkx.setupsubset(SS_MAP, "Options")[attr]=val
  245.  
  246. SMALL = .0001
  247.  
  248. def snapFunc(o, current, editor):
  249.     
  250.     class pack:
  251.         "stick stuff here"
  252.     pack.current=current
  253.     pack.face=o
  254.     pack.tagged = gettaggedplane(editor)
  255.     pack.editor=editor
  256.     pack.separation,=readWithDefault('snapobject_separation',(1,))
  257.  
  258.     def setup(self, pack=pack, editor=editor):
  259.         
  260.         src = self.src
  261.         self.pack=pack
  262.         for (dattr, oattr) in (('swivel','snapobject_noswivel'), ('orient','snapobject_noorient'), ('shift','snapobject_noshift' )):
  263.             src[dattr]=readFlippedSetupCheckVal(oattr)
  264.         
  265.         src['separation']=readWithDefault('snapobject_separation',(1,))
  266.         src['separation'] = pack.separation,
  267.         pack.doswivel=pack.dotilt=pack.doshift=0
  268.         if src['swivel']:
  269.             pack.doswivel=1
  270.         if src['orient']:
  271.             pack.dotilt=1
  272.             pack.doswivel=1
  273.         if src['shift']:
  274.             pack.doshift=1
  275.         
  276.     def action(self, pack=pack, editor=editor):
  277.         src = self.src
  278.         for (dattr, oattr) in (('swivel','snapobject_noswivel'), ('orient','snapobject_noorient'), ('shift','snapobject_noshift' )):
  279.             writeFlippedSetupCheckVal(oattr,src[dattr])
  280.         
  281.         writeWithDefault('snapobject_separation',src['separation'], (1,))
  282.         pack.separation,=src['separation']
  283.  
  284.     SnapObjectDlg('snapobject',editor,setup,action)
  285.     
  286. #
  287. # Build the 'select a parent' menu
  288. #
  289. def snapItems(current, editor, restricted):
  290.     name = current.shortname
  291.     
  292.     def snapClick(m, current=current, editor=editor):
  293.         snapFunc(editor.layout.explorer.sellist[0], current, editor)
  294.     
  295.     snapItem=qmenu.item("Snap to tagged",snapClick)
  296.     snapItem.state=qmenu.default
  297.     
  298.     item = qmenu.popup(name,[snapItem],None,"|This is the name of some group or brush entity that contains what you have selected.\n\nLook at its submenu for stuff you can do!\n\nIf there's a bar in the menu, then the `Restrict Selections' menu item is checked, and you can only select stuff above the bar.")
  299.     item.menuicon = current.geticon(1)
  300.     item.object = current
  301.     return item
  302.  
  303. def parentSnapPopup(o, editor):
  304.     parentSnap = qmenu.popup("&Snap Parent", hint = "|The submenu that appears comprises the currently selected object at the top, and below it, the map objects (polys, groups & brush entities) that are above it in the group tree-structure.\n\nIf you put the cursor over one of these, you will get the snap-to-selected face command.")
  305.     item = mapmadsel.buildParentPopup(o, parentSnap, snapItems, editor)
  306.     tagged=gettaggedplane(editor)
  307.     if tagged is None:
  308.          item.state=qmenu.disabled
  309.     return item
  310.  
  311.  
  312. #
  313. # Put it on the face menu
  314. #  (actually no, we import into maptagside)
  315. #
  316. #def snapfacemenu(o, editor, oldmenu=quarkpy.mapentities.FaceType.menu.im_func):
  317. #    "the new right-mouse menu for faces"
  318. #    menu = oldmenu(o, editor)
  319. #
  320. #    menu[:0] = [parentSnapPopup(o,editor),
  321. #
  322. #                quarkpy.qmenu.sep]
  323. #    return menu  
  324.  
  325. #quarkpy.mapentities.FaceType.menu = snapfacemenu
  326.  
  327. # $Log: #
  328. #